package com.asiainfo.framework.config;


import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.*;

import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static com.asiainfo.framework.config.DataSourceAutoConfig.getDataSourceBeanName;
import static com.asiainfo.framework.config.DataSourceProperties.isPrimary;


/**
 * <pre>
 * 事务配置 (优先使用注解方式配置事务)
 *  基于注解声明式事务管理 @Transactional
 *  基于pointcut expression表达式声明式事务管理
 * </pre>
 */
@Configuration
@Import(TransactionManagerAutoConfig.Registrar.class)
public class TransactionManagerAutoConfig {
    private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.asiainfo.*.service..*(..))";
    private static final int AOP_TIME_OUT = 10000;

    static class Registrar implements ImportBeanDefinitionRegistrar {

        /**
         * 注册事务管理器
         */
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                            BeanDefinitionRegistry registry) {
            DataSourceProperties.get().forEach((name, value) -> {
                registry.registerBeanDefinition(
                        name + Advisor.class.getName(),
                        BeanDefinitionBuilder
                                .rootBeanDefinition(TransactionBeanFactory.class, "createInstance")
                                .addConstructorArgReference(getDataSourceBeanName(name))
                                .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
                                .setPrimary(isPrimary(value))
                                .getBeanDefinition()
                );
            });
        }

    }

    static class TransactionBeanFactory {

        public static Advisor createInstance(DataSource dataSource) {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
            TransactionInterceptor txAdvice = txAdvice(transactionManager);
            Advisor advisor = txAdvisor(txAdvice);
            return advisor;
        }

        private static PlatformTransactionManager txManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }

        private static TransactionInterceptor txAdvice(PlatformTransactionManager transactionManager) {
            //注解驱动事务
            TransactionAttributeSource annotationTransactionAttributeSource
                    = new AnnotationTransactionAttributeSource();

            //pointcut expression表达式声明式事务
            TransactionAttributeSource nameMatchTransactionAttributeSource =
                    getNameMatchTransactionAttributeSource();

            TransactionAttributeSource compositeTransactionAttributeSource
                    = new CompositeTransactionAttributeSource(
                    annotationTransactionAttributeSource,
                    nameMatchTransactionAttributeSource
            );
            TransactionInterceptor txAdvice =
                    new TransactionInterceptor(transactionManager, compositeTransactionAttributeSource);
            return txAdvice;
        }

        private static NameMatchTransactionAttributeSource getNameMatchTransactionAttributeSource() {
            NameMatchTransactionAttributeSource nameMatchTransactionAttributeSource
                    = new NameMatchTransactionAttributeSource();
            Map<String, TransactionAttribute> methodMap = new HashMap<>();

            /** 当前存在事务就使用当前事务，当前不存在事务就创建一个新的事务 */
            RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
            requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Throwable.class)));
            requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            requiredTx.setTimeout(AOP_TIME_OUT);
            methodMap.put("add*", requiredTx);
            methodMap.put("insert*", requiredTx);
            methodMap.put("save*", requiredTx);
            methodMap.put("create*", requiredTx);
            methodMap.put("update*", requiredTx);
            methodMap.put("modify*", requiredTx);
            methodMap.put("edit*", requiredTx);
            methodMap.put("del*", requiredTx);
            methodMap.put("remove*", requiredTx);
            methodMap.put("clear*", requiredTx);

            /** 只读事务，不做更新操作 */
            RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
            readOnlyTx.setReadOnly(true);
            readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            /** 其他方法无事务，只读 */
            methodMap.put("*", readOnlyTx);
            nameMatchTransactionAttributeSource.setNameMap(methodMap);
            return nameMatchTransactionAttributeSource;
        }

        private static Advisor txAdvisor(TransactionInterceptor txAdvice) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
            return new DefaultPointcutAdvisor(pointcut, txAdvice);
        }

    }
}
